package cc.rengu.oltp.utility.util;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

/**
 * 3DES对称密钥算法工具类
 */
public class DES3Util {
    // 双倍长密钥
    public static byte[] encryptMode3DESDouble(byte[] keybyte, byte[] src) {
        // 密钥前半部分str = DES(str ,k21)
        byte[] frontByte = new byte[8];
        System.arraycopy(keybyte, 0, frontByte, 0, 8);
        byte[] step1 = encryptModeDES(frontByte, src);
        if (step1 == null) {
            return null;
        }
        // 密钥后半部分str = UDES(str ,k22)
        byte[] afterByte = new byte[8];
        System.arraycopy(keybyte, 8, afterByte, 0, 8);
        byte[] step2 = decryptModeDES(afterByte, step1);
        if (step2 == null) {
            return null;
        }
        // str = DES(str ,k21)
        return encryptModeDES(frontByte, step2);
    }

    // 双倍长密钥
    public static byte[] decryptMode3DESDouble(byte[] keybyte, byte[] src) {
        // 密钥前半部分str = UDES(str ,k21)
        byte[] frontByte = new byte[8];
        System.arraycopy(keybyte, 0, frontByte, 0, 8);
        byte[] step1 = decryptModeDES(frontByte, src);
        if (step1 == null) {
            return null;
        }
        // 密钥后半部分str = DES(str ,k22)
        byte[] afterByte = new byte[8];
        System.arraycopy(keybyte, 8, afterByte, 0, 8);
        byte[] step2 = encryptModeDES(afterByte, step1);
        if (step2 == null) {
            return null;
        }
        // str = UDES(str ,k21)
        return decryptModeDES(frontByte, step2);
    }

    // src为被加密的数据缓冲区（源）
    private static byte[] encryptModeDES(byte[] keybyte, byte[] src) {
        // 实例化IvParameterSpec对象，使用指定的初始化向量
        // 实例化SecretKeySpec类，根据字节数组来构造SecretKey
        SecretKeySpec key = new SecretKeySpec(keybyte, "DES");
        // 创建密码器
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance("DES/ECB/NoPadding");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            e.printStackTrace();
            return null;
        }
        // 用秘钥初始化Cipher对象
        try {
            cipher.init(Cipher.ENCRYPT_MODE, key);
        } catch (InvalidKeyException e) {
            e.printStackTrace();
            return null;
        }
        // 执行加密操作
        byte[] encryptedData = null;
        byte[] padData = padding(src, cipher.getBlockSize());
        try {
            encryptedData = cipher.doFinal(padData);
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            e.printStackTrace();
            return null;
        }

        return encryptedData;// Base64.encodeToString(encryptedData,
        // Base64.DEFAULT);
    }

    // src为加密后的缓冲区
    private static byte[] decryptModeDES(byte[] keybyte, byte[] src) {
        // 实例化IvParameterSpec对象，使用指定的初始化向量
        // 实例化SecretKeySpec类，根据字节数组来构造SecretKey
        SecretKeySpec key = new SecretKeySpec(keybyte, "DES");
        // 创建密码器
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance("DES/ECB/NoPadding");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            e.printStackTrace();
            return null;
        }
        // 用秘钥初始化Cipher对象
        try {
            cipher.init(Cipher.DECRYPT_MODE, key);
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
        // 执行解密操作
        byte[] decryptedData = null;
        try {
            decryptedData = cipher.doFinal(src);
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            e.printStackTrace();
        }

        return decryptedData;// new String(decryptedData);
    }

    /**
     * 生成密钥的校验值
     *
     * @param key 密钥
     * @return 校验值
     */
    public static byte[] genCheckValue(byte[] key) {
        byte[] iv = new byte[16];
        Arrays.fill(iv, (byte) 0);
        return encryptMode3DESDouble(key, iv);
    }

    static byte[] ecbModeDecrypt(byte[] key, byte[] data) {
        byte[] res = null;
        String algorithm = "DESede/ECB/NoPadding";
        try {
            Cipher cipher = Cipher.getInstance(algorithm);
            SecretKeySpec spec = new SecretKeySpec(key, "DESede");
            byte[] padData = DES3Util.padding(data, cipher.getBlockSize());
            cipher.init(Cipher.DECRYPT_MODE, spec);
            res = cipher.doFinal(padData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;
    }

    static byte[] ecbModeEncrypt(byte[] key, byte[] data) {
        byte[] res = null;
        String algorithm = "DESede/ECB/NoPadding";
        try {
            Cipher cipher = Cipher.getInstance(algorithm);
            SecretKeySpec spec = new SecretKeySpec(key, "DESede");
            byte[] padData = DES3Util.padding(data, cipher.getBlockSize());
            cipher.init(Cipher.ENCRYPT_MODE, spec);
            res = cipher.doFinal(padData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;
    }

    /**
     * 3DES Cbc模式 加密
     *
     * @param key  密钥
     * @param data 明文
     * @return 密文
     */
    public static byte[] cbcModeEncrypt(byte[] key, byte[] data) {
        byte[] encData = null;
        String algorithm = "DESede/CBC/NoPadding";
        try {
            Cipher cipher = Cipher.getInstance(algorithm);
            SecretKeySpec spec = new SecretKeySpec(key, "DESede");
            IvParameterSpec ips = getIv(cipher.getBlockSize());
            data = padding(data, cipher.getBlockSize());
            cipher.init(Cipher.ENCRYPT_MODE, spec, ips);
            encData = cipher.doFinal(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encData;
    }

    /**
     * 3DES Cbc模式 解密
     *
     * @param key  密钥
     * @param data 密文
     * @return 明文
     */
    public static byte[] desEdeCbcDecrypt(byte[] key, byte[] data) {
        byte[] decData = null;
        String algorithm = "DESede/CBC/NoPadding";
        try {
            Cipher cipher = Cipher.getInstance(algorithm);
            SecretKeySpec spec = new SecretKeySpec(key, "DESede");
            IvParameterSpec ips = getIv(cipher.getBlockSize());
            byte[] padData = padding(data, cipher.getBlockSize());
            cipher.init(Cipher.DECRYPT_MODE, spec, ips);
            decData = cipher.doFinal(padData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return decData;
    }

    /**
     * 补足长度
     *
     * @param src 源数据
     * @param len 长度
     * @return 补足长度后的数据
     */
    private static byte[] padding(byte[] src, int len) {
        int paddingLength = len - src.length % len;
        if (len == paddingLength) {
            return src;
        }
        byte[] newsrc = new byte[src.length + paddingLength];
        System.arraycopy(src, 0, newsrc, 0, src.length);
        return newsrc;
    }

    /**
     * 初始化向量
     *
     * @param len 长度
     * @return 初始化向量
     */
    private static IvParameterSpec getIv(int len) {
        /*使用 IV 的例子是反馈模式中的密码，如，CBC 模式中的 DES 和使用 OAEP 编码操作的 RSA 密码*/
        byte[] zero = new byte[len];
        return new IvParameterSpec(zero);
    }
}
